﻿#include "time_system.hh"
#include "../box/service_box.hh"
#include "../config/box_config.hh"
#include "string_util.hh"
#include <iomanip>
#include <sstream>
#include <stdexcept>

#if (!defined(WIN32) && !defined(_WIN64))
#include <cstdlib>
#include <cstring>
#include <sys/time.h>
#endif

#if (defined(WIN32) || defined(_WIN64))
#include <Windows.h>
#endif /* defined(WIN32) || defined(_WIN64) */

namespace kratos {
namespace time_system {

/**
 * 获取时区偏移.
 */
int get_timezone();
/**
 * 获取本地时间与UTC的时间差，秒.
 */
int time_offset();
/**
 * 全局毫秒时间戳.
 */
static time_t global_millionsecond_timestamp = 0;
/**
 * 全局秒时间戳.
 */
static time_t global_second_timestamp = 0;
/**
 * 全局UTC日期.
 */
static Datetime global_datetime_utc;
/**
 * 全局本地日期.
 */
static Datetime global_datetime_tz;
/**
 * 全局UTC tm.
 */
static tm global_tm_utc;
/**
 * 全局本地tm.
 */
static tm global_tm_tz;
/**
 * 获取时区偏移，毫秒.
 */
static int timezone_ = time_offset();
/**
 * 是否开启时间系统.
 */
static bool global_is_open = false;
/**
 * 星期.
 */
constexpr static Weekday weekdays[] = {
    Weekday::Sunday,   Weekday::Monday, Weekday::Tuesday, Weekday::Wednesday,
    Weekday::Thursday, Weekday::Friday, Weekday::Saturday};
/**
 * 一天的秒数.
 */
typedef std::chrono::duration<int, std::ratio<3600 * 24>> days;
/**
 * 函数gmtime包装，线程安全
 *
 * \param [OUT] tm 返回
 * \param now 当前时间戳, 秒
 */
void gmtime_os(struct tm *tm, time_t now) {
#if (defined(WIN32) || defined(_WIN64))
  gmtime_s(tm, &now);
#else
  gmtime_r(&now, tm);
#endif // (defined(WIN32) || defined(_WIN64))
}
/**
 * 函数localtime包装，线程安全
 *
 * \param [OUT] tm 返回
 * \param now 当前时间戳, 秒
 */
void localtime_os(struct tm *tm, time_t now) {
#if (defined(WIN32) || defined(_WIN64))
  localtime_s(tm, &now);
#else
  localtime_r(&now, tm);
#endif // (defined(WIN32) || defined(_WIN64))
}

int time_offset() {
  time_t gmt, rawtime = time(NULL);
  struct tm *ptm;

#if !defined(WIN32)
  struct tm gbuf;
  ptm = gmtime_r(&rawtime, &gbuf);
#else
  ptm = gmtime(&rawtime);
#endif
  ptm->tm_isdst = -1;
  gmt = mktime(ptm);
  return (int)difftime(rawtime, gmt);
}

int get_timezone() {
  if (timezone_) {
    return timezone_;
  }
  timezone_ = time_offset();
  return timezone_;
}

void update_utc(const std::chrono::system_clock::time_point &now,
                std::time_t timet) {
  gmtime_os(&global_tm_utc, timet);
  global_datetime_utc = {global_tm_utc.tm_year + 1900, global_tm_utc.tm_mon + 1,
                         global_tm_utc.tm_mday,        global_tm_utc.tm_hour,
                         global_tm_utc.tm_min,         global_tm_utc.tm_sec};
  global_datetime_utc.wday = weekdays[global_tm_utc.tm_wday];
}

void update_tz(const std::chrono::system_clock::time_point &now,
               std::time_t timet) {
  localtime_os(&global_tm_tz, timet);
  global_datetime_tz = {global_tm_tz.tm_year + 1900, global_tm_tz.tm_mon + 1,
                        global_tm_tz.tm_mday,        global_tm_tz.tm_hour,
                        global_tm_tz.tm_min,         global_tm_tz.tm_sec};
  global_datetime_tz.wday = weekdays[global_tm_tz.tm_wday];
}

void update_timestamp(const std::chrono::system_clock::time_point &now,
                      std::time_t timet) {
  auto millionSecondsPoint =
      std::chrono::time_point_cast<std::chrono::milliseconds>(now);
  auto millionSecondsDuration =
      std::chrono::duration_cast<std::chrono::milliseconds>(
          millionSecondsPoint.time_since_epoch());
  global_millionsecond_timestamp = millionSecondsDuration.count();
  global_second_timestamp = timet;
}

void TimeSystem::update() {
  auto timet = time(nullptr);
  auto now = std::chrono::system_clock::from_time_t(timet);
  update_utc(now, timet);
  update_tz(now, timet);
  update_timestamp(now, timet);
}

void TimeSystem::update(std::time_t current) {
  auto timet = current / 1000;
  auto now = std::chrono::system_clock::from_time_t(timet);
  update_utc(now, timet);
  update_tz(now, timet);
  update_timestamp(now, timet);
}

bool TimeSystem::open_time_system(kratos::service::ServiceBox *box) {
  if (!box) {
    return false;
  }
  if (box->get_config().has_attribute("is_open_time_system")) {
    global_is_open =
        (box->get_config().get_string("is_open_time_system") == "true");
  }
  return global_is_open;
}

bool TimeSystem::is_open_time_system() { return global_is_open; }

std::time_t TimeSystem::get_nanosecond_os() {
  auto now = std::chrono::system_clock::now();
  auto nanoSecondsDuration =
      std::chrono::duration_cast<std::chrono::nanoseconds>(
          now.time_since_epoch());
  return nanoSecondsDuration.count();
}

std::time_t TimeSystem::get_millionsecond() {
  return global_millionsecond_timestamp;
}

std::time_t TimeSystem::get_millionsecond_os() {
  auto now = std::chrono::system_clock::now();
  auto millionSecondsDuration =
      std::chrono::duration_cast<std::chrono::milliseconds>(
          now.time_since_epoch());
  return millionSecondsDuration.count();
}

std::time_t TimeSystem::get_second() { return global_second_timestamp; }

std::time_t TimeSystem::get_second_os() { return time(nullptr); }

const Datetime &TimeSystem::UTC::get_datetime_utc() {
  return global_datetime_utc;
}

const Datetime TimeSystem::UTC::get_datetime_utc(std::time_t t) {
  struct tm tms;
  memset(&tms, 0, sizeof(tms));
  gmtime_os(&tms, t);
  Datetime dt = {tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
                 tms.tm_hour,        tms.tm_min,     tms.tm_sec};
  dt.wday = weekdays[tms.tm_wday];
  return dt;
}

const Datetime &TimeSystem::CIVIL::get_datetime() { return global_datetime_tz; }

const Datetime TimeSystem::CIVIL::get_datetime(std::time_t t) {
  struct tm tms;
  memset(&tms, 0, sizeof(tms));
  localtime_os(&tms, t);
  Datetime dt = {tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
                 tms.tm_hour,        tms.tm_min,     tms.tm_sec};
  dt.wday = weekdays[tms.tm_wday];
  return dt;
}

const tm *TimeSystem::UTC::get_tm_utc() { return &global_tm_utc; }

const tm *TimeSystem::CIVIL::get_tm() { return &global_tm_tz; }

int TimeSystem::CIVIL::get_days(std::time_t t) {
  struct tm tms;
  memset(&tms, 0, sizeof(tms));
  localtime_os(&tms, t);
  auto tday =
      days_from_civil<int>(tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday);
  auto nowDay =
      days_from_civil<int>(global_datetime_tz.year, global_datetime_tz.month,
                           global_datetime_tz.day);
  return nowDay - tday;
}

int TimeSystem::CIVIL::get_days(std::time_t t1, std::time_t t2) {
  struct tm tm1;
  struct tm tm2;
  memset(&tm1, 0, sizeof(tm1));
  memset(&tm2, 0, sizeof(tm2));
  localtime_os(&tm1, t1);
  localtime_os(&tm2, t2);
  auto t1day =
      days_from_civil<int>(tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday);
  auto t2day =
      days_from_civil<int>(tm2.tm_year + 1900, tm2.tm_mon + 1, tm2.tm_mday);
  return t2day - t1day;
}

int TimeSystem::UTC::get_days_utc(std::time_t t) {
  struct tm tms;
  memset(&tms, 0, sizeof(tms));
  gmtime_os(&tms, t);
  auto tday =
      days_from_civil<int>(tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday);
  auto nowDay =
      days_from_civil<int>(global_datetime_utc.year, global_datetime_utc.month,
                           global_datetime_utc.day);
  return nowDay - tday;
}

int TimeSystem::UTC::get_days_utc(std::time_t t1, std::time_t t2) {
  struct tm tm1;
  struct tm tm2;
  memset(&tm1, 0, sizeof(tm1));
  memset(&tm2, 0, sizeof(tm2));
  gmtime_os(&tm1, t1);
  gmtime_os(&tm2, t2);
  auto t1day =
      days_from_civil<int>(tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday);
  auto t2day =
      days_from_civil<int>(tm2.tm_year + 1900, tm2.tm_mon + 1, tm2.tm_mday);
  return t2day - t1day;
}

std::time_t
TimeSystem::UTC::get_second_from_datetime_utc(const Datetime &datetime) {
  return CIVIL::get_second_from_datetime(datetime) + (time_t)get_timezone();
}

std::time_t TimeSystem::get_utc_diff_second() { return get_timezone(); }

std::time_t
TimeSystem::CIVIL::get_second_from_datetime(const Datetime &datetime) {
  struct tm tm1;
  memset(&tm1, 0, sizeof(tm1));
  tm1.tm_year = datetime.year - 1900;
  tm1.tm_mon = datetime.month - 1;
  tm1.tm_mday = datetime.day;
  tm1.tm_hour = datetime.hour;
  tm1.tm_min = datetime.minute;
  tm1.tm_sec = datetime.second;
  return mktime(&tm1);
}

bool TimeSystem::CIVIL::in_same_day(std::time_t t1, std::time_t t2) {
  return (0 == get_days(t1, t2));
}

bool TimeSystem::CIVIL::in_same_day(std::time_t t) {
  return in_same_day(t, global_second_timestamp);
}

bool TimeSystem::CIVIL::in_same_week(std::time_t t1, std::time_t t2) {
  if (t1 > t2) {
    auto t = t2;
    t2 = t1;
    t1 = t;
  }
  struct tm tm1;
  struct tm tm2;
  memset(&tm1, 0, sizeof(tm1));
  memset(&tm2, 0, sizeof(tm2));
  localtime_os(&tm1, t1);
  localtime_os(&tm2, t2);
  if ((tm1.tm_year != tm2.tm_year) || (tm1.tm_mon != tm2.tm_mon)) {
    return false;
  }
  int yday_diff = tm2.tm_yday - tm1.tm_yday;
  if (yday_diff > 7) {
    return false;
  }
  int wday1 = tm1.tm_wday ? tm1.tm_wday : 7;
  int wday2 = tm2.tm_wday ? tm2.tm_wday : 7;
  if (wday1 > wday2) {
    return false;
  }
  return true;
}

bool TimeSystem::CIVIL::in_same_week(std::time_t t) {
  return in_same_week(t, global_second_timestamp);
}

bool TimeSystem::CIVIL::in_same_month(std::time_t t1, std::time_t t2) {
  struct tm tm1;
  struct tm tm2;
  memset(&tm1, 0, sizeof(tm1));
  memset(&tm2, 0, sizeof(tm2));
  localtime_os(&tm1, t1);
  localtime_os(&tm2, t2);
  if ((tm1.tm_year != tm2.tm_year) || (tm1.tm_mon != tm2.tm_mon)) {
    return false;
  }
  return true;
}

bool TimeSystem::CIVIL::in_same_month(std::time_t t) {
  return in_same_month(t, global_second_timestamp);
}

bool TimeSystem::UTC::in_same_day_utc(std::time_t t1, std::time_t t2) {
  return (0 == get_days_utc(t1, t2));
}

bool TimeSystem::UTC::in_same_day_utc(std::time_t t) {
  return in_same_day_utc(t, global_second_timestamp);
}

bool TimeSystem::UTC::in_same_week_utc(std::time_t t1, std::time_t t2) {
  if (t1 > t2) {
    auto t = t2;
    t2 = t1;
    t1 = t;
  }
  struct tm tm1;
  struct tm tm2;
  memset(&tm1, 0, sizeof(tm1));
  memset(&tm2, 0, sizeof(tm2));
  gmtime_os(&tm1, t1);
  gmtime_os(&tm2, t2);
  if ((tm1.tm_year != tm2.tm_year) || (tm1.tm_mon != tm2.tm_mon)) {
    return false;
  }
  int yday_diff = tm2.tm_yday - tm1.tm_yday;
  if (yday_diff > 7) {
    return false;
  }
  int wday1 = tm1.tm_wday ? tm1.tm_wday : 7;
  int wday2 = tm2.tm_wday ? tm2.tm_wday : 7;
  if (wday1 > wday2) {
    return false;
  }
  return true;
}

bool TimeSystem::UTC::in_same_week_utc(std::time_t t) {
  return in_same_week_utc(t, global_second_timestamp);
}

bool TimeSystem::UTC::in_same_month_utc(std::time_t t1, std::time_t t2) {
  struct tm tm1;
  struct tm tm2;
  memset(&tm1, 0, sizeof(tm1));
  memset(&tm2, 0, sizeof(tm2));
  gmtime_os(&tm1, t1);
  gmtime_os(&tm2, t2);
  if ((tm1.tm_year != tm2.tm_year) || (tm1.tm_mon != tm2.tm_mon)) {
    return false;
  }
  return true;
}

bool TimeSystem::UTC::in_same_month_utc(std::time_t t) {
  return in_same_month_utc(t, global_second_timestamp);
}

Datetime::Datetime() noexcept
    : year(0), month(0), day(0), wday(Weekday::Sunday), hour(0), minute(0),
      second(0) {}

Datetime::Datetime(std::time_t t) noexcept {
  struct tm tm1;
  memset(&tm1, 0, sizeof(tm1));
  localtime_os(&tm1, t);
  year = tm1.tm_year + 1900;
  month = tm1.tm_mon + 1;
  day = tm1.tm_mday;
  hour = tm1.tm_hour;
  minute = tm1.tm_min;
  second = tm1.tm_sec;
  wday = weekdays[tm1.tm_wday];
}

Datetime::Datetime(const std::string &fmt) noexcept(false) {
  if (!util::is_date_string_fmt1(fmt)) {
    throw std::invalid_argument("Invalid Datetime string:" + fmt);
  }
  int yy, mm, dd, h, m, s;
  sscanf(fmt.c_str(), "%d/%d/%d %d:%d:%d", &yy, &mm, &dd, &h, &m, &s);
  struct tm tm1;
  memset(&tm1, 0, sizeof(tm1));
  tm1.tm_year = yy - 1900;
  tm1.tm_mon = mm - 1;
  tm1.tm_mday = dd;
  tm1.tm_hour = h;
  tm1.tm_min = m;
  tm1.tm_sec = s;
  localtime_os(&tm1, mktime(&tm1));
  year = tm1.tm_year + 1900;
  month = tm1.tm_mon + 1;
  day = tm1.tm_mday;
  hour = tm1.tm_hour;
  minute = tm1.tm_min;
  second = tm1.tm_sec;
  wday = weekdays[tm1.tm_wday];
}

Datetime::Datetime(const std::initializer_list<int> &params) noexcept
    : year(0), month(0), day(0), wday(Weekday::Sunday), hour(0), minute(0),
      second(0) {
  auto it = params.begin();
  if (it != params.end()) {
    year = *it++;
  } else {
    return;
  }
  if (it != params.end()) {
    month = *it++;
  } else {
    return;
  }
  if (it != params.end()) {
    day = *it++;
  } else {
    return;
  }
  if (it != params.end()) {
    hour = *it++;
  } else {
    return;
  }
  if (it != params.end()) {
    minute = *it++;
  } else {
    return;
  }
  if (it != params.end()) {
    second = *it++;
  } else {
    return;
  }
}

std::time_t Datetime::get_second() {
  return TimeSystem::CIVIL::get_second_from_datetime(*this);
}

std::time_t Datetime::get_second_utc() {
  return TimeSystem::UTC::get_second_from_datetime_utc(*this);
}

std::string Datetime::to_string() {
  std::stringstream s;
  s << year << "/" << std::setfill('0') << std::setw(2) << month << "/"
    << std::setfill('0') << std::setw(2) << day << " " << std::setfill('0')
    << std::setw(2) << hour << ":" << std::setfill('0') << std::setw(2)
    << minute << ":" << std::setfill('0') << std::setw(2) << second;
  return s.str();
}

} // namespace time_system
} // namespace kratos
